Applying LINQ Queries to Collection Objects

Beyond pulling results from a simple array of data, LINQ query expressions can also manipulate data within members of the System.Collections.Generic namespace, such as the List<T> type. Create a new Console Application project named LinqOverCollections, and define a basic Car class that maintains a current speed, color, make, and pet name as shown in the following code:

class Car
{
    public string PetName {get; set;}
    public string Color {get; set;}
    public int Speed {get; set;}
    public string Make {get; set;}
}

Now, within your Main() method define a local List<T> variable of type Car, and make use of object initialization syntax to fill the list with a handful of new Car objects:

static void Main(string[] args)
{
    Console.WriteLine("***** LINQ over Generic Collections *****\n");

    // Make a List<> of Car objects.
    List<Car> myCars = new List<Car>() {
        new Car{ PetName = "Henry", Color = "Silver", Speed = 100, Make = "BMW"},
        new Car{ PetName = "Daisy", Color = "Tan", Speed = 90, Make = "BMW"},
        new Car{ PetName = "Mary", Color = "Black", Speed = 55, Make = "VW"},
        new Car{ PetName = "Clunker", Color = "Rust", Speed = 5, Make = "Yugo"},
        new Car{ PetName = "Melvin", Color = "White", Speed = 43, Make = "Ford"}
    };

    Console.ReadLine();
}

Accessing Contained Subobjects

Applying a LINQ query to a generic container is no different than doing so with a simple array, as LINQ to Objects can be used on any type implementing IEnumerable<T>. This time, your goal is to build a query expression to select only the Car objects within the myCars list, where the speed is greater than 55.

Once you get the subset, you will print out the name of each Car object by calling the PetName property. Assume you have the following helper method (taking a List<Car> parameter), which is called from within Main():

static void GetFastCars(List<Car> myCars)
{
    // Find all Car objects in the List<>, where the Speed is
    // greater than 55.
    var fastCars = from c in myCars where c.Speed > 55 select c;

    foreach (var car in fastCars)
    {
        Console.WriteLine("{0} is going too fast!", car.PetName);
    }
}

Notice that your query expression is only grabbing items from the List<T> where the Speed property is greater than 55. If you run the application, you will find that “Henry” and “Daisy” are the only two items that match the search criteria.

If you want to build a more complex query, you might wish to only find the BMWs that have a Speed value above 90. To do so, simply build a compound Boolean statement using the C# && operator

static void GetFastBMWs(List<Car> myCars)
{
    // Find the fast BMWs!
    var fastCars = from c in myCars where c.Speed > 90 && c.Make == "BMW" select c;
    foreach (var car in fastCars)
    {
        Console.WriteLine("{0} is going too fast!", car.PetName);
    }
}

In this case, the only pet name printed out is “Henry”.

Applying LINQ Queries to Nongeneric Collections

Recall that the query operators of LINQ are designed to work with any type implementing IEnumerable<T> (either directly or via extension methods). Given that System.Array has been provided with such necessary infrastructure, it may surprise you that the legacy (nongeneric) containers within System.Collections have not. Thankfully, it is still possible to iterate over data contained within nongeneric collections using the generic Enumerable.OfType<T>() extension method.

The OfType<T>() method is one of the few members of Enumerable that does not extend generic types. When calling this member off a nongeneric container implementing the IEnumerable interface (such as the ArrayList), simply specify the type of item within the container to extract a compatible IEnumerable<T> object. In code, you can store this data point using an implicitly typed variable.

Consider the following new method which fills an ArrayList with a set of Car objects (be sure to import the System.Collections namespace into your Program.cs file).

static void LINQOverArrayList()
{
    Console.WriteLine("***** LINQ over ArrayList *****");

    // Here is a nongeneric collection of cars.
    ArrayList myCars = new ArrayList() {
        new Car{ PetName = "Henry", Color = "Silver", Speed = 100, Make = "BMW"},
        new Car{ PetName = "Daisy", Color = "Tan", Speed = 90, Make = "BMW"},
        new Car{ PetName = "Mary", Color = "Black", Speed = 55, Make = "VW"},
        new Car{ PetName = "Clunker", Color = "Rust", Speed = 5, Make = "Yugo"},
        new Car{ PetName = "Melvin", Color = "White", Speed = 43, Make = "Ford"}
    };

    // Transform ArrayList into an IEnumerable<T>-compatible type.
    var myCarsEnum = myCars.OfType<Car>();

    // Create a query expression targeting the compatible type.
    var fastCars = from c in myCarsEnum where c.Speed > 55 select c;

    foreach (var car in fastCars)
    {
        Console.WriteLine("{0} is going too fast!", car.PetName);
    }
}

Similar to the previous examples, this method, when called from Main() will only display the names "Henry" and "Daisy", based on the format of our LINQ query.

Filtering Data Using OfType<T>()

As you know, nongeneric types are capable of containing any combination of items, as the members of these containers (again, such as the ArrayList) are prototyped to receive System.Objects. For example, assume an ArrayList contains a variety of items, only a subset of which are numerical. If you want to obtain a subset that contains only numerical data, you can do so using OfType<T>(), since it filters out each element whose type is different from the given type during the iterations:

static void OfTypeAsFilter()
{
    // Extract the ints from the ArrayList.
    ArrayList myStuff = new ArrayList();
    myStuff.AddRange(new object[] { 10, 400, 8, false, new Car(), "string data" });
    var myInts = myStuff.OfType<int>();

    // Prints out 10, 400, and 8.
    foreach (int i in myInts)
    {
        Console.WriteLine("Int value: {0}", i);
    }
}

Great! At this point, you have had a chance to apply LINQ queries to arrays, generic collections, and nongeneric collections. These containers held both C# primitive types (integers, string data) as well as custom classes. The next task is to learn about many additional LINQ operators which can be used to build more complex, and useful queries.

Source Code The LinqOverCollections project can be found under the Chapter 13 subdirectory.